/*
 * Created on 2004-07-09
 */
package org.opencube.oms.mapping;

import info.fingo.util.Text;

import java.lang.reflect.Constructor;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

import org.opencube.oms.OMSElement;
import org.opencube.oms.OMSStructure;
import org.opencube.oms.OMSStructureException;
import org.opencube.oms.meta.OMSMetaDataException;
import org.opencube.util.Constants;

/**
 * @author <a href="mailto:maciek@fingo.pl">Maciej Mroczko - FINGO</a>
 *
 * TODO comments
 */
public abstract class Entity
{	
	/**
	 * The date format used for string representation: iso = 'yyyy-MM-dd'T'HH:mm:ss.SSS'
	 */
	public static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
	
	/**
	 * The underlying oms element
	 */
	protected OMSElement element = null;
	
	
	/* The formatter to convert the data representation */
	private static final SimpleDateFormat formatter = new SimpleDateFormat( DATE_FORMAT );
	
	/**
	 * Creates new entity of the scheme represented by the given element
	 * 
	 * @param element the oms element to wrap 
	 */
	public Entity( OMSElement element )
	{
		this.element = element;
	}

	/**
	 * Creates new entity of the given scheme
	 * 
	 * @param namespacePath the schema namespace path
	 * @param schemeName the schema name
	 */
	protected Entity( String namespacePath, String schemeName )
	{
		this.element = new OMSStructure().createElement( namespacePath, schemeName );
	}
	
	/**
	 * Returns the string representation of the given date
	 * 
	 * @param date the date to format
	 * 
	 * @return The string representation of the given date
	 */
	protected static String formatDate( Date date )
	{
		return date == null ? null : formatter.format( date );
	}
	
	/**
	 * Returns the date object parsed from the given sting using specified format
	 * 
	 * @param date the string to parse
	 * 
	 * @return The date value of the string
	 * 
	 * @throws OMSMetaDataException when the given string is not in the specified format
	 */
	protected static Date parseDate( String date ) throws OMSMetaDataException
	{
		try
		{
			return date == null ? null : formatter.parse( date );
		}
		catch( ParseException e )
		{
			throw new OMSMetaDataException( "Cannot parse the date: '" + date + "'" , e);
		}
	}

	/**
	 * Returns the underlying element
	 * 
	 * @return The underlying element
	 */
	public OMSElement getElement()
	{
		return element;
	}
	
	/**
	 * Marks the given entity as deleted
	 */
	public void delete() throws OMSStructureException
	{
		element.delete();
	}
	
	/**
	 * Returns the underlying element's bookmark
	 * 
	 * @return The underlying element's bookmark
	 */
	public String getElementBookmark()
	{
		return element.getBookmark();
	}
	
	/**
	 * Returns the underlying element's creater id 
	 * 
	 * @return The underlying element's creater id
	 */
	public String getCreater()
	{
		return element.getCreater();
	}
	
	/**
	 * Returns the underlying element's creating date
	 * 
	 * @return The underlying element's creating date
	 */
	public Date getCreatingDate()
	{
		return element.getCreatingDate();
	}
	
	/**
	 * Returns the underlying element's display name
	 * 
	 * @return The underlying element's display name
	 */
	public String getElementDisplayName()
	{
		return element.getDisplayName();
	}
	
	/**
	 * Returns the underlying element's id
	 * 
	 * @return The underlying element's id
	 */
	public String getElementId()
	{
		return element.getId();
	}
	
	/**
	 * Returns the underlying element's modifier id 
	 * 
	 * @return The underlying element's modifier id
	 */
	public String getModifier()
	{
		return element.getModifier();
	}
	
	/**
	 * Returns the underlying element's modifying date
	 * 
	 * @return The underlying element's modifying date
	 */
	public Date getModifyingDate()
	{
		return element.getModifyingDate();
	}
	
	/**
	 * Sets the underlying element's bookmark
	 * 
	 * @param bookmark the bookmark to set
	 */
	public void setElementBookmark(String bookmark) throws OMSStructureException
	{
		element.setBookmark(bookmark);
	}
	
	/**
	 * Sets the underlying element's display name
	 * 
	 * @param displayName the display name to set
	 */
	public void setElementDisplayName(String displayName) throws OMSStructureException
	{
		element.setDisplayName(displayName);
	}
	
	/* Merges the given entity */
	private void merge( Entity entity ) throws OMSStructureException
	{
		element.getOMSStructure().merge( entity.getElement().getOMSStructure() );
	}
	
	protected void set( Entity entity, String attribute, boolean composition, String key )
	throws OMSStructureException
	{
		OMSElement[] joined = element.getJoinedElements( attribute );
		// is already joined
		if( joined != null && joined.length > 0 )
		{
			OMSElement e = joined[ 0 ];
			// return if already joined with the enity
			if( entity != null && e.getId().equals( entity.getElement().getId() ) )
			{
				return;
			}
			element.disconnect( attribute, e );
		}
		add( entity, attribute, composition, key );
	}
	
	protected void add( Entity entity, String attribute, boolean composition, String key )
	throws OMSStructureException
	{
		if( entity != null && !contains( attribute, entity ) )
		{
			if( composition )
			{
				merge( entity );
				element.compose( attribute, entity.getElement(), key );
			}
			else
			{
				element.associate( attribute, entity.getElement(), key );
			}
		}
	}
	
	protected void remove( Entity entity, String attribute )
	throws OMSStructureException
	{
		element.disconnect( attribute, entity.getElement() );
	}
	
	protected int removeAll( String attribute )
	throws OMSStructureException
	{
		int removed = 0;
		OMSElement[] joined = element.getJoinedElements( attribute );
		for (int i = 0; joined != null && i < joined.length; i++)
		{
			element.disconnect( attribute, joined[ i ] );
			removed++;
		}
		return removed;
	}
	public boolean isNew()
	{
		return element.isNew();
	}
	public boolean isRemoved()
	{
		return element.isRemoved();
	}
	public boolean isUpdated()
	{
		return element.isUpdated();
	}
	
	protected int removeFromAll( String namespacePath, String schemeName, String attributeName  )
	throws OMSStructureException
	{
		int removed = 0;
		OMSElement[] elements = element.getJoiningElements( namespacePath, schemeName, attributeName );
		for (int i = 0; elements != null && i < elements.length; i++)
		{
			elements[ i ].disconnect( attributeName, element );
			removed++;
		}
		return removed;
	}
	
	protected boolean contains( String attribute, Entity entity )
	{
		OMSElement[] joined = element.getJoinedElements( attribute );
		for (int i = 0; joined != null && i < joined.length; i++)
		{
			if( joined[ i ].getId().equals( entity.getElementId() ) )
			{
				return true;
			}
		}
		
		return false;
	}
	
	public static Entity getNewEntity( String basePackage, OMSElement element ) throws OMSMetaDataException
	{
		if( element == null )
		{
			return null;
		}
		try
		{
			String className = basePackage + Text.substitute( element.getNamespacePath(), Constants.STR_DOT, Constants.STR_UNDERSCORE ) + Constants.STR_DOT + element.getSchemeName();
			className = Text.substitute( className, Constants.STR_SLASH, Constants.STR_DOT );
			
			@SuppressWarnings("unchecked") 
			Class<Entity> c = (Class<Entity>) Class.forName( className );
			Class[] paramClasses = new Class[]
			{
				OMSElement.class	
			};
			Constructor<Entity> constructor = c.getConstructor( paramClasses );
			Object[] params = new Object[]
			{
				element,
			};
			return constructor.newInstance( params );
		}
		catch( Exception e )
		{
			throw new OMSMetaDataException( "Error while instantiation entity for element: " + element + ": " + e.getMessage(), e );
		}
	}
		
}
